package com.example.rxohosaudio.ability;

import com.example.rxharmonyaudio.*;
import com.example.rxohosaudio.ResourceTable;
import io.reactivex.Observable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.harmony.schedulers.HarmonySchedulers;
import io.reactivex.internal.functions.Functions;
import io.reactivex.schedulers.Schedulers;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;

import ohos.agp.components.*;
import ohos.agp.utils.Color;
import ohos.agp.window.dialog.ToastDialog;
import ohos.bundle.IBundleManager;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.audio.AudioManager;
import ohos.media.common.AudioProperty;
import ohos.media.common.Source;
import ohos.media.common.StorageProperty;
import ohos.media.recorder.Recorder;
import ohos.multimodalinput.event.TouchEvent;

import java.io.File;
import java.util.*;

public class FileAbility extends Ability implements AudioRecorder.OnErrorListener {
    private static final String TAG = "FileAbility";
    private static final HiLogLabel label = new HiLogLabel(
            HiLog.LOG_APP, 0x00201, TAG);

    private static final int MY_PERMISSION_REQUEST = 1213;
    private static final int MIN_AUDIO_LENGTH_SECONDS = 2;
    private static final int AUDIO_SAMPLE_RATE_HZ = 8000;
    private static final int AUDIO_BIT_RATE_HZ = 192000;
    private static final int COUNT_DOWN_TIME = 12;
    private static final int RECORDER_TOTAL_TIME = 15;


    private StackLayout mFlIndicator;
    private Text mTvPressToSay;
    private Text mTvLog;
    private Text mTvRecordingHint;

    private List<Image> mIvVoiceIndicators;

    private AudioRecorder mAudioRecorder;
    private RxAudioPlayer mRxAudioPlayer;
    private File mAudioFile;
    private Disposable mRecordDisposable;
    private Queue<File> mAudioFiles = new LinkedList<>();
    private CompositeDisposable compositeDisposable = new CompositeDisposable();

    @Override
    public void onStart(final Intent intent) {
        super.onStart(intent);
        setUIContent(ResourceTable.Layout_ability_file);

        mFlIndicator = (StackLayout) findComponentById(ResourceTable.Id_mFlIndicator);
        mTvPressToSay = (Text) findComponentById(ResourceTable.Id_mTvPressToSay);
        mTvLog = (Text) findComponentById(ResourceTable.Id_mTvLog);

        mTvRecordingHint = (Text) findComponentById(ResourceTable.Id_mTvRecordingHint);
        Button mBtnPlay = (Button) findComponentById(ResourceTable.Id_mBtnPlay);

        mIvVoiceIndicators = new ArrayList<>();
        mIvVoiceIndicators.add((Image) findComponentById(ResourceTable.Id_mIvVoiceIndicator1));
        mIvVoiceIndicators.add((Image) findComponentById(ResourceTable.Id_mIvVoiceIndicator2));
        mIvVoiceIndicators.add((Image) findComponentById(ResourceTable.Id_mIvVoiceIndicator3));
        mIvVoiceIndicators.add((Image) findComponentById(ResourceTable.Id_mIvVoiceIndicator4));
        mIvVoiceIndicators.add((Image) findComponentById(ResourceTable.Id_mIvVoiceIndicator5));
        mIvVoiceIndicators.add((Image) findComponentById(ResourceTable.Id_mIvVoiceIndicator6));
        mIvVoiceIndicators.add((Image) findComponentById(ResourceTable.Id_mIvVoiceIndicator7));

        if (!isPermissionGranted()) {
            //Toast to notify user to reason of request permission.
            new ToastDialog(getContext())
                    .setText("Your need to granted the user_Storage and microPhone permission to record.")
                    .show();
            requestPermission();
        }

        mAudioRecorder = AudioRecorder.getInstance();
        mRxAudioPlayer = RxAudioPlayer.getInstance();
        mAudioRecorder.setOnErrorListener(this::onError);
        mTvPressToSay.setTouchEventListener((component, touchEvent) -> {
            switch (touchEvent.getAction()) {
                case TouchEvent.PRIMARY_POINT_DOWN:
                    HiLog.error(label, "TouchEvent.PRIMARY_POINT_DOWN");
                    press2Record();
                    break;
                case TouchEvent.PRIMARY_POINT_UP:
                    HiLog.error(label, "TouchEvent.PRIMARY_POINT_UP");
                    release2Send();
                    break;
                case TouchEvent.CANCEL:
                    HiLog.error(label, "TouchEvent.CANCEL");
                    release2Send();
                    break;
                default:
                    break;
            }
            return true;
        });

        mBtnPlay.setClickedListener(component -> {
            startPlay();
        });
    }

    @Override
    public void onRequestPermissionsFromUserResult(final int requestCode, final String[] permissions,
                                                   final int[] grantResults) {
        if (requestCode == MY_PERMISSION_REQUEST) {
            if (grantResults.length > 0
                    && grantResults[0] == IBundleManager.PERMISSION_GRANTED) {
                new ToastDialog(getContext())
                        .setText(" permission granted !!")
                        .show();
            } else {
                new ToastDialog(getContext())
                        .setText("request permission first!")
                        .show();
            }
        }
    }

    private void press2Record() {
        mTvPressToSay.setTextColor(Color.BLUE);
        mTvRecordingHint.setText(ResourceTable.String_voice_msg_input_hint_speaking);

        if (!isPermissionGranted()) {
            //Toast to notify user to reason of request permission.
            new ToastDialog(getContext())
                    .setText("Your need to granted the user_Storage and microPhone permission to record.")
                    .show();
            requestPermission();
        } else {
            recordAfterPermissionGranted();
        }
    }

    private boolean isPermissionGranted() {
        boolean isPermissionsGranted =
                (verifySelfPermission("ohos.permission.MICROPHONE") == IBundleManager.PERMISSION_GRANTED)
                        && (verifySelfPermission("ohos.permission.READ_USER_STORAGE") == IBundleManager.PERMISSION_GRANTED)
                        && (verifySelfPermission("ohos.permission.WRITE_USER_STORAGE") == IBundleManager.PERMISSION_GRANTED)
                        && (verifySelfPermission("ohos.permission.MODIFY_AUDIO_SETTINGS") == IBundleManager.PERMISSION_GRANTED)
                        && (verifySelfPermission("ohos.permission.READ_MEDIA") == IBundleManager.PERMISSION_GRANTED)
                        && (verifySelfPermission("ohos.permission.WRITE_MEDIA") == IBundleManager.PERMISSION_GRANTED);
        return isPermissionsGranted;
    }

    private void requestPermission() {
        if (canRequestPermission("ohos.permission.MICROPHONE")
                || canRequestPermission("ohos.permission.READ_USER_STORAGE")
                || canRequestPermission("ohos.permission.WRITE_USER_STORAGE")
                || canRequestPermission("ohos.permission.MODIFY_AUDIO_SETTINGS")
                || canRequestPermission("ohos.permission.READ_MEDIA")
                || canRequestPermission("ohos.permission.WRITE_MEDIA")) {

            getContext().requestPermissionsFromUser(new String[]{"ohos.permission.MICROPHONE",
                    "ohos.permission.READ_USER_STORAGE",
                    "ohos.permission.WRITE_USER_STORAGE",
                    "ohos.permission.MODIFY_AUDIO_SETTINGS",
                    "ohos.permission.READ_MEDIA",
                    "ohos.permission.WRITE_MEDIA"}, MY_PERMISSION_REQUEST);
        }
    }

    public void startPlay() {
        mTvLog.setText("");
        if (!mAudioFiles.isEmpty()) {
            File audioFile = mAudioFiles.poll();
            compositeDisposable.add(mRxAudioPlayer.play(
                    PlayConfig.file(audioFile)
                            .streamType(AudioManager.AudioVolumeType.STREAM_VOICE_CALL)
                            .build())
                    .subscribeOn(Schedulers.io())
                    .observeOn(HarmonySchedulers.mainThread())
                    .subscribe(Functions.emptyConsumer(), Throwable::printStackTrace,
                            this::startPlay));
        }
    }

    private void recordAfterPermissionGranted() {
        HiLog.error(label, "recordAfterPermissionGranted");
        mRecordDisposable = io.reactivex.Observable
                .fromCallable(() -> {
                    mAudioFile = new File("data/data/com.example.rxohosaudio/"
                            + File.separator + System.nanoTime() + ".mp4");
                    HiLog.error(label, "to prepare record");
                    //source:from MIC
                    Source source = new Source();
                    source.setRecorderAudioSource(Recorder.AudioSource.MIC);

                    //sample_rate :192000
                    //bit_rate :192000
                    //encode :aac
                    AudioProperty audioProperty = new AudioProperty.Builder()
                            .setRecorderSamplingRate(AUDIO_SAMPLE_RATE_HZ)
                            .setRecorderBitRate(AUDIO_BIT_RATE_HZ)
                            .setRecorderAudioEncoder(Recorder.AudioEncoder.AAC)
                            .build();
                    StorageProperty storageProperty = new StorageProperty.Builder()
                            .setRecorderFile(mAudioFile)
                            .setRecorderMaxDurationMs(-1)
                            .setRecorderMaxFileSizeBytes(-1)
                            .build();

                    return mAudioRecorder.prepareRecord(source, Recorder.OutputFormat.MPEG_4,
                            audioProperty, storageProperty);
                })//todo:find the resolution to add the wav files and play.
//                .flatMap(b -> {
//                    HiLog.error(label,"prepareRecord success");
//                    HiLog.error(label,"to play audio_record_ready:");
//                    Observable<Boolean> ret = mRxAudioPlayer.play(
//                            PlayConfig.res(getApplicationContext(), getContext().getResourceManager().
//                                    getRawFileEntry("resources/media/audio_record_ready.wav"))
//                                    .build());
//                    HiLog.error(label,"Observable<Boolean> ret:" + ret.getClass().getComponentType());
//                    return ret;
//                })
                .doOnComplete(() -> {
                    HiLog.debug(label, "audio_record_ready play finished");
                    new EventHandler(EventRunner.getMainEventRunner()).postSyncTask(() ->
                            mFlIndicator.setVisibility(Component.VISIBLE));
                    mAudioRecorder.startRecord();
                })
                .doOnNext(b -> HiLog.info(label, "startRecord success"))
                .flatMap(o -> RxAmplitude.from(mAudioRecorder))
//                .compose(bindToLifecycle())
                .subscribeOn(Schedulers.io())
                .observeOn(HarmonySchedulers.mainThread())
                .subscribe(level -> {
                    int progress = mAudioRecorder.progress();
                    HiLog.info(label, "amplitude: " + level + ", progress: " + progress);

                    refreshAudioAmplitudeView(level);

                    if (progress >= COUNT_DOWN_TIME) {
                        mTvRecordingHint.setText(String.format(
                                "%d s left", RECORDER_TOTAL_TIME - progress));
                        if (progress == RECORDER_TOTAL_TIME) {
                            release2Send();
                        }
                    }
                }, Throwable::printStackTrace);
    }

    private void release2Send() {
//        mTvPressToSay.setBackgroundResource(R.drawable.button_press_to_say_bg);
        HiLog.error(label, "release2Send");
        mTvPressToSay.setTextColor(Color.BLACK);
        mFlIndicator.setVisibility(Component.INVISIBLE);
        if (mRecordDisposable != null && !mRecordDisposable.isDisposed()) {
            mRecordDisposable.dispose();
            mRecordDisposable = null;
        }

        compositeDisposable.add(Observable
                .fromCallable(() -> {
                    int seconds = mAudioRecorder.stopRecord();
                    HiLog.debug(label, "stopRecord: " + seconds);
                    if (seconds >= MIN_AUDIO_LENGTH_SECONDS) {
                        mAudioFiles.offer(mAudioFile);
                        return true;
                    }
                    return false;
                })
//                .compose(bindToLifecycle())
                .subscribeOn(Schedulers.io())
                .observeOn(HarmonySchedulers.mainThread())
                .subscribe(added -> {
                    if (added) {
                        mTvLog.setText(mTvLog.getText() + "\n"
                                + "audio file " + mAudioFile.getName() + " added");
                    }
                }, Throwable::printStackTrace));
    }


    private void refreshAudioAmplitudeView(final int level) {
        int end = Math.min(level, mIvVoiceIndicators.size());

        List<Image> sublist1 = mIvVoiceIndicators.subList(0, end);
        for (Image value : sublist1) {
            value.setVisibility(Component.VISIBLE);
        }

        List<Image> sublist2 = mIvVoiceIndicators.subList(end, mIvVoiceIndicators.size());
        for (Image image : sublist2) {
            image.setVisibility(Component.INVISIBLE);
        }
    }

    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mRxAudioPlayer != null) {
            mRxAudioPlayer.stopPlay();
        }
        compositeDisposable.dispose();
    }

    @Override
    public void onForeground(final Intent intent) {
        super.onForeground(intent);
    }

    @Override
    public void onError(final int error) {
        new EventHandler(EventRunner.getMainEventRunner()).postSyncTask(() ->
                new ToastDialog(getContext())
                        .setText("Error code: " + error)
                        .show());
    }
}
